import datetime
import json

import joblib
import matplotlib.pyplot as plt
import numpy
import numpy as np
import pandas as pds
import statsmodels.formula.api as smf
from flask import request
from plotly.offline import iplot
from statsmodels.stats.multitest import multipletests

from app.Controllers.BaseController import BaseController
from app.Package.entity.UnivariateAnalysisRequestBody import UnivariateAnalysisRequestBody
from app.Package.pyChemometrics.plotting_utils import _lineplots as Lineplot, manhattan_plot, interactive_manhattan
from app.Service.ChemometricsTutorialsServices.CommonService import readData, getSavePath, getWebPath, getProjectInfo, \
    getImagePath, getWebImagePath


class UnivariateAnalysisService:

    # 需要进行UnivariateAnalysis的通用部分，读取项目的信息和request表单中的信息用于填入UA类中
    def UACommomProcess(self):
        userId, projectId, data, label, coordinate, savePath = getProjectInfo()
        formdata = request.data.decode('utf-8')
        formjson = json.loads(formdata)
        UA = UnivariateAnalysisRequestBody(data, label, coordinate, savePath)
        UA.set_data(formjson)
        return UA

    # 解析UA的请求表单，从而获取
    def unpackUnivariateAnalysisRequestBody(self, input_data):
        xfile = input_data.xfile
        yfile = input_data.yfile
        ppmfile = input_data.ppmfile
        thread_path = input_data.thread_path
        return xfile, yfile, ppmfile, thread_path

    # 获取单变量分析需要的dataset
    def getModel(self, xfile, yfile, ppmfile, outliers_index=None):
        X, Y, ppm = readData(xfile, yfile, ppmfile, outliers_index)
        dataset = pds.DataFrame(X)
        # 分类特征
        Ycode = np.array(Y)
        for i in range(Y.shape[1]):
            Ycode[:, i] = pds.Categorical(Y[:, i]).codes
            dataset.insert(i, "Y" + str(i), pds.Categorical(Y[:, i]).codes)

        # Prepare the dataframe for use with model fitting
        dataset.columns = dataset.columns.astype(str)
        varnames = 'ppm_' + dataset.columns[2::]
        newnames = []
        for i in range(Y.shape[1]):
            newnames.append("Y" + str(i))
        newnames.extend(varnames)
        dataset.columns = newnames
        return dataset, ppm, Y.shape[1]

    # 构建SingleRegression模型
    def getSingleRegressionModel(self, input_data):
        xfile, yfile, ppmfile, thread_path = self.unpackUnivariateAnalysisRequestBody(input_data)
        dataset, ppm, y_shape = self.getModel(xfile, yfile, ppmfile, input_data.outliers_index)
        chooseType = input_data.chooseType
        # 构建列表存储分数
        # p-value
        pval = list()
        # regression coefficient
        beta = list()
        # P-value for the F-test
        fpval = list()
        # r2 for the regression modelt
        r2 = list()

        singleTest = ' ~ C(Y' + str(chooseType) + ')'
        for curr_variable in dataset.iloc[:, y_shape:]:
            # Formula for current variable
            fm = curr_variable + singleTest
            mod = smf.ols(formula=fm, data=dataset)
            res = mod.fit()
            pval.append(res.pvalues[1])
            beta.append(res.params[1])
            fpval.append(res.f_pvalue)
            r2.append(res.rsquared)

        pval = np.array(pval)
        beta = np.array(beta)
        r2 = np.array(r2)
        fpval = np.array(fpval)

        # Adjusting the first analysis without 2
        by_res = multipletests(pval, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
        p_byadj = by_res[1]

        # Adjusting the first analysis without 2
        by_res_f = multipletests(fpval, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
        p_byadj_f = by_res_f[1]

        results_dframe = pds.DataFrame(
            np.c_[ppm, pval, beta, r2, fpval, p_byadj, p_byadj_f],
            columns=['ppm', 'p-value',
                     'beta', 'r2', 'f-test_pval', 'q-value', 'q-value-f'])
        Filename = "UnivariateAnalysis"
        Path = getSavePath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"
        results_dframe.to_csv(Path, index=False)
        webPath = getWebPath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"
        # result_table = dict(ppm=ppm, p_value=pval, beta=beta, r2=r2, f_test_pval=fpval, q_value=p_byadj, q_value_f=p_byadj_f)
        resultData = {"webPath": webPath}
        return BaseController().successData(msg='成功获取分析结果', data=resultData)

    # 构建多变量回归模型
    # 需要进行修改，从两个变成多个
    def getDoubleRegressionModel(self, input_data):
        xfile, yfile, ppmfile, thread_path = self.unpackUnivariateAnalysisRequestBody(input_data)
        dataset, ppm, y_shape = self.getModel(xfile, yfile, ppmfile, input_data.outliers_index)
        chooseType = input_data.chooseType
        secondType = input_data.secondType
        # 构建列表存储分数
        pval1 = list()
        pval2 = list()
        beta1 = list()
        beta2 = list()
        fpval = list()
        r2 = list()

        mutiType = ' ~ C(Y' + str(chooseType) + ')' + '+ C(Y' + str(secondType) + ')'
        for curr_variable in dataset.iloc[:, y_shape:]:
            # Fit each column
            fm = curr_variable + mutiType
            mod = smf.ols(formula=fm, data=dataset)
            res = mod.fit()
            pval1.append(res.pvalues[1])
            pval2.append(res.pvalues[2])
            beta1.append(res.params[1])
            beta2.append(res.params[2])
            fpval.append(res.f_pvalue)
            r2.append(res.rsquared)

        pval1 = np.array(pval1)
        beta1 = np.array(beta1)
        pval2 = np.array(pval2)
        beta2 = np.array(beta2)
        r2 = np.array(r2)
        fpval = np.array(fpval)

        # Adjusting the analysis with 1 and 2
        by_res_y1 = multipletests(pval1, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
        p_y1_byadj = by_res_y1[1]

        by_res_f = multipletests(fpval, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
        p_byadj_f = by_res_f[1]

        by_res_y2 = multipletests(pval2, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
        p_y2_byadj = by_res_y2[1]

        results_dframe = pds.DataFrame(np.c_[ppm, pval1, beta1, p_y1_byadj, pval2, beta2, p_y2_byadj,
                                             r2, fpval, p_byadj_f],
                                       columns=['ppm', 'p-value',
                                                'beta', 'q-value', 'p-value2', 'beta2',
                                                'q-value2', 'r2', 'f-test_pval', 'q-value-f'])
        Filename = "UnivariateAnalysisNotSingle"
        Path = getSavePath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"
        results_dframe.to_csv(Path, index=False)
        webPath = getWebPath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"

        # Manhattan plot style visualization for the effect of the y1 covariate
        manhattan_plot(p_y1_byadj, beta1, xvalues=ppm)
        imageFilename = "Manhattan_plot_y1"
        imagePath, shortPath = getImagePath(thread_path, imageFilename)
        plt.savefig(imagePath, dpi=input_data.dpi, bbox_inches='tight')
        webImagePath_y1 = getWebImagePath(thread_path, shortPath)

        # Manhattan plot style visualization for the effect of the y2 covariate
        manhattan_plot(p_y2_byadj, beta2, xvalues=ppm)
        imageFilename = "Manhattan_plot_y2"
        imagePath, shortPath = getImagePath(thread_path, imageFilename)
        plt.savefig(imagePath, dpi=input_data.dpi, bbox_inches='tight')
        webImagePath_y2 = getWebImagePath(thread_path, shortPath)


        iplot(interactive_manhattan(p_y2_byadj, beta2, sig=0.05, xvalues=ppm))
        imageFilename = "interactive_manhattan_plot_y2"
        imagePath, shortPath = getImagePath(thread_path, imageFilename)
        plt.savefig(imagePath, dpi=input_data.dpi, bbox_inches='tight')
        webImagePath_interactive_manhattan_y2 = getWebImagePath(thread_path, shortPath)

        resultData = {"webPath": webPath, "webImagePath_y1": webImagePath_y1, "webImagePath_y2": webImagePath_y2,
                      "webImagePath_interactive_manhattan_y2": webImagePath_interactive_manhattan_y2}
        return BaseController().successData(msg='成功获取分析结果', data=resultData)

    # 两个变量之间的关系怎么处理（弄不了）
    # # 构建多变量回归模型
    # def getMultivariablegressionModel(self, input_data):
    #     xfile, yfile, ppmfile, thread_path = self.unpackUnivariateAnalysisRequestBody(input_data)
    #     dataset, ppm, y_shape = self.getModel(xfile, yfile, ppmfile, input_data.outliers_index)
    #     typeList = input_data.typeList
    #     # 构建列表存储分数
    #     pval = list()
    #     beta = list()
    #     fpval = list()
    #     r2 = list()
    #
    #     mutiType = ''
    #
    #     for type in typeList:
    #         mutiType += ' ~ C(Y' + str(type) + ')'
    #
    #     for curr_variable in dataset.iloc[:, y_shape:]:
    #         # Fit each column
    #         fm = curr_variable + mutiType
    #         mod = smf.ols(formula=fm, data=dataset)
    #         res = mod.fit()
    #         pval.append(res.pvalues)
    #         beta.append(res.params)
    #         fpval.append(res.f_pvalue)
    #         r2.append(res.rsquared)
    #
    #     pval = np.array(pval)
    #     beta = np.array(beta)
    #     r2 = np.array(r2)
    #     fpval = np.array(fpval)
    #
    #     # Adjusting the analysis with 1 and 2
    #     by_res_y1 = multipletests(pval1, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
    #     p_y1_byadj = by_res_y1[1]
    #
    #     by_res_f = multipletests(fpval, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
    #     p_byadj_f = by_res_f[1]
    #
    #     by_res_y2 = multipletests(pval2, alpha=input_data.multipletestsAlpha, method=input_data.multipletestsMethod)
    #     p_y2_byadj = by_res_y2[1]
    #
    #     results_dframe = pds.DataFrame(np.c_[ppm, pval1, beta1, p_y1_byadj, pval2, beta2, p_y2_byadj,
    #                                          r2, fpval, p_byadj_f],
    #                                    columns=['ppm', 'p-value',
    #                                             'beta', 'q-value', 'p-value2', 'beta2',
    #                                             'q-value2', 'r2', 'f-test_pval', 'q-value-f'])
    #     Filename = "UnivariateAnalysisNotSingle"
    #     Path = getSavePath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"
    #     results_dframe.to_csv(Path, index=False)
    #     webPath = getWebPath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"
    #     resultData = {"webPath": webPath}
    #     return BaseController().successData(msg='成功获取分析结果', data=resultData)

    # 进行重采样，获取结果，需要修改成多变量的
    def bootstrapping(self, input_data):
        xfile, yfile, ppmfile, thread_path = self.unpackUnivariateAnalysisRequestBody(input_data)
        dataset, ppm, y_shape = self.getModel(xfile, yfile, ppmfile, input_data.outliers_index)

        results = joblib.Parallel(n_jobs=8, verbose=5, pre_dispatch='1.5*n_jobs')(
            joblib.delayed(self.bootstrap_model)(i, 50, dataset, input_data.chooseType, input_data.secondType) for i in
            range(y_shape, dataset.shape[1]))

        # mean and standard deviation for regression coefficients and wald test p-values
        # Genotype
        mean_beta_y1 = np.array([x[:, 2].mean() for x in results])
        std_beta_y1 = np.array([x[:, 2].std() for x in results])
        mean_p_y1 = np.array([x[:, 0].mean() for x in results])
        std_p_y1 = np.array([x[:, 0].std() for x in results])
        # Age
        mean_beta_y2 = np.array([x[:, 3].mean() for x in results])
        std_beta_y2 = np.array([x[:, 3].std() for x in results])
        mean_p_y2 = np.array([x[:, 1].mean() for x in results])
        std_p_y2 = np.array([x[:, 1].std() for x in results])

        results_dframe = pds.DataFrame(
            np.c_[ppm, mean_beta_y1, std_beta_y1, mean_p_y1, std_p_y1, mean_beta_y2, std_beta_y2,
                  mean_p_y2, std_p_y2],
            columns=['ppm', 'mean_beta_y1',
                     'std_beta_y1', 'mean_p_y1', 'std_p_y1', 'mean_beta_y2',
                     'std_beta_y2', 'mean_p_y2', 'std_p_y2'])
        Filename = "BootstrappingResult"
        Path = getSavePath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"
        results_dframe.to_csv(Path, index=False)
        webPath = getWebPath(thread_path) + Filename + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".csv"

        # Visualization of regression coefficients with Bootstrap confidence intervals
        Lineplot(mean=mean_beta_y1, error=std_beta_y1, xaxis=numpy.squeeze(ppm))
        plt.gca().invert_xaxis()
        imageFilename = "Visualization_of_regression_coefficients_y1"
        imagePath, shortPath = getImagePath(thread_path, imageFilename)
        plt.savefig(imagePath, dpi=input_data.dpi, bbox_inches='tight')
        webImagePath_y1 = getWebImagePath(thread_path, shortPath)

        # Visualization of regression coefficients with Bootstrap confidence intervals
        Lineplot(mean=mean_beta_y2, error=std_beta_y2, xaxis=numpy.squeeze(ppm))
        plt.gca().invert_xaxis()
        imageFilename = "Visualization_of_regression_coefficients_y2"
        imagePath, shortPath = getImagePath(thread_path, imageFilename)
        plt.savefig(imagePath, dpi=input_data.dpi, bbox_inches='tight')
        webImagePath_y2 = getWebImagePath(thread_path, shortPath)

        resultData = {"webPath": webPath, "webImagePath_y1": webImagePath_y1, "webImagePath_y2": webImagePath_y2}
        return BaseController().successData(msg='成功获取分析结果', data=resultData)

    # 构建重采样的模型
    def bootstrap_model(self, variable, n_boot, dataset, chooseType, secondType):
        boot_stats = np.zeros((n_boot, 6))
        mutiType = ' ~ C(Y' + str(chooseType) + ')' + '+ C(Y' + str(secondType) + ')'

        for boot_iter in range(n_boot):
            boot_sample = np.random.choice(dataset.shape[0], dataset.shape[0], replace=True)
            fm = dataset.columns[variable] + mutiType
            mod = smf.ols(formula=fm, data=dataset.iloc[boot_sample, :])
            res = mod.fit()
            boot_stats[boot_iter, 0] = res.pvalues[1]
            boot_stats[boot_iter, 1] = res.pvalues[2]
            boot_stats[boot_iter, 2] = res.params[1]
            boot_stats[boot_iter, 3] = res.params[2]
            boot_stats[boot_iter, 4] = res.f_pvalue
            boot_stats[boot_iter, 5] = res.rsquared

        return boot_stats

    # 重采样后进行绘图
